package com.study.graph.repository;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.study.graph.config.NebulaConstant;
import com.study.graph.model.BaseEdge;
import com.study.graph.model.BaseVertex;
import com.study.graph.model.NebulaResult;
import com.study.graph.model.annotation.EdgeMapping;
import com.study.graph.model.annotation.FieldMapping;
import com.study.graph.model.annotation.FieldType;
import com.study.graph.model.annotation.VertexMapping;
import com.study.graph.util.SqlBuild;
import com.vesoft.nebula.client.graph.data.ResultSet;
import com.vesoft.nebula.client.graph.exception.IOErrorException;
import com.vesoft.nebula.client.graph.net.Session;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Slf4j
@Component
public class BaseGraphRepository<T extends BaseVertex> {

    @Resource
    protected Session session;

    // # 插入不包含属性的点。
    public boolean insertVertex(T entity) {
        Class<?> c = entity.getClass();
        VertexMapping pointMapping = c.getAnnotation(VertexMapping.class);
        if (Objects.isNull(pointMapping)) {
            throw new RuntimeException("实体类注解错误");
        }
        //获取字段名称与类型
        Map<String, String> fieldMap = Stream.of(c.getDeclaredFields())
                .map(field -> field.getAnnotation(FieldMapping.class))
                .collect(Collectors.toMap(FieldMapping::value, FieldMapping::type));
        // 获取字段与值
        Map<String, Object> fieldValueMap = Stream.of(c.getDeclaredFields())
                .collect(Collectors.toMap(field1 -> {
                    FieldMapping fieldMapping = field1.getAnnotation(FieldMapping.class);
                    return fieldMapping.value();
                }, field2 -> getFieldValue(entity, field2)));

        String valuesStr = fieldMap.entrySet().stream()
                .map(entry -> {
                    if (Objects.equals(FieldType.String, entry.getValue())) {
                        return "'" + fieldValueMap.get(entry.getKey()) + "'";
                    }
                    if (Objects.equals(FieldType.Int, entry.getValue())) {
                        return "" + fieldValueMap.get(entry.getKey());
                    }
                    throw new RuntimeException("字段类型错误");
                })
                .collect(Collectors.joining(","));

        final String fieldStr = String.join(",", fieldMap.keySet());
        StringBuilder stmt = new StringBuilder(64);
        stmt.append("INSERT VERTEX ").append(pointMapping.value()).append("(").append(fieldStr).append(") ");
        stmt.append(" VALUES ").append("'").append(entity.getAlias()).append("':(").append(valuesStr).append(")");
        try {
            ResultSet resp = session.execute(stmt.toString());
            return resp.isSucceeded();
        } catch (IOErrorException e) {
            return false;
        }
    }

    private static Object getFieldValue(Object entity, Field field) {
        try {
            field.setAccessible(true);
            return field.get(entity);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    //创建边
    public boolean createEdge(BaseEdge entity) {
        Class<?> c = entity.getClass();
        EdgeMapping edgeMapping = c.getAnnotation(EdgeMapping.class);
        if (Objects.isNull(edgeMapping)) {
            throw new RuntimeException("边注解错误");
        }
        Map<String, String> fieldMap = Stream.of(c.getDeclaredFields())
                .map(field -> field.getAnnotation(FieldMapping.class))
                .collect(Collectors.toMap(FieldMapping::value, FieldMapping::type));
        final String fieldStr = fieldMap.entrySet().stream()
                .map(entry -> entry.getKey() + " " + entry.getValue())
                .collect(Collectors.joining(","));
        String sql = "CREATE EDGE IF NOT EXISTS " + edgeMapping.value() + "(" + fieldStr + ")";
        return execute(sql);
    }

    public boolean execute(String sql) {
        try {
            ResultSet resp = session.execute(sql);
            return resp.isSucceeded();
        } catch (IOErrorException e) {
            return false;
        }
    }

    // 插入边
    public boolean insertEdge(BaseEdge baseEdge, String leftId, String rightId) throws Exception {
        //   INSERT EDGE e2 (name, age) VALUES "11"->"13":("n1", 1);
        String sql = "insert edge %s(%s) values \"%s\" -> \"%s\":(%s);";
        SqlBuild edge = parseEdge(baseEdge);
        String stmt = String.format(sql, edge.getName(), edge.getField(), leftId, rightId, edge.getValues());
        return execute(stmt);
    }

    private static <T> SqlBuild parseEdge(BaseEdge entity) throws NoSuchFieldException {
        Class<?> clazz = entity.getClass();
        EdgeMapping annotation = clazz.getAnnotation(EdgeMapping.class);
        if (Objects.isNull(annotation)) {
            throw new RuntimeException("边注解错误");
        }
        String tagName = annotation.value();
        Field[] declaredFields = clazz.getDeclaredFields();
        StringBuilder filedString = new StringBuilder();
        StringBuilder valueString = new StringBuilder();
        Long id = null;
        for (int i = 0; i < declaredFields.length; i++) {
            Field declaredField = declaredFields[i];
            // 获取属性名称
            String name = declaredField.getName();
            // 获取自定义注解 FieldAutoMapping
            FieldMapping fieldMapping = declaredField.getAnnotation(FieldMapping.class);
            if (Objects.isNull(fieldMapping)) {
                continue;
            }
            String type = fieldMapping.type();
            Field field = clazz.getDeclaredField(fieldMapping.value());
            Object value = getFieldValue(entity, field);
            filedString.append(name);
            Object valueFormat = format(value, type);
            if ("id".equals(name)) {
                id = (Long) value;
            }
            valueString.append(valueFormat);
            if (i != declaredFields.length - 1) {
                filedString.append(",");
                valueString.append(",");
            }
        }
        return new SqlBuild(id, tagName, filedString.toString(), valueString.toString());
    }

    private static Object format(Object value, String type) {
        if (Objects.equals(FieldType.String, type)) {
            return "\"" + value + "\"";
        }
        return value;
    }

    // 插入边与关系
    public boolean insertEdges(BaseEdge edgeEntity, String relationship) {
        //   INSERT EDGE e2 (name, age) VALUES "11"->"13":("n1", 1);

        return false;
    }


    public <T> NebulaResult<T> queryObject(String stmt, Class<T> tClass) {
        NebulaResult<T> nebulaResult = executeObject(stmt);
        if (Objects.isNull(nebulaResult.getData())) {
            return nebulaResult;
        }

        nebulaResult.setData(nebulaResult.getData().stream()
                .map(jsonObj -> {
                    // {"v":{"player.name":"xxx","player.age":18}}
                    // 数据解析时候需要加注解才能解析到  @JSONField(name="player.name")
                    System.out.println(jsonObj);
                    JSONObject jsonObject = (JSONObject) jsonObj;
                    return JSONObject.toJavaObject(jsonObject, tClass);
                })
                .collect(Collectors.toList())
        );
        return nebulaResult;
    }

    public NebulaResult executeObject(String stmt) {
        JSONObject jsonObject = executeJson(stmt);
        return JSONObject.toJavaObject(jsonObject, NebulaResult.class);
    }

    public JSONObject executeJson(String stmt) {
        JSONObject restJson = new JSONObject();
        try {
            String result = session.executeJson(stmt);
            System.out.println(result);
            JSONObject jsonObject = JSON.parseObject(result);
            JSONObject errors = jsonObject.getJSONArray(NebulaConstant.NebulaJson.ERRORS.getKey()).getJSONObject(0);
            restJson.put(NebulaConstant.NebulaJson.CODE.getKey(), errors.getInteger(NebulaConstant.NebulaJson.CODE.getKey()));
            if (errors.getInteger(NebulaConstant.NebulaJson.CODE.getKey()) != 0) {
                restJson.put(NebulaConstant.NebulaJson.MESSAGE.getKey(), errors.getString(NebulaConstant.NebulaJson.MESSAGE.getKey()));
                return restJson;
            }
            JSONObject results = jsonObject.getJSONArray(NebulaConstant.NebulaJson.RESULTS.getKey()).getJSONObject(0);
            JSONArray columns = results.getJSONArray(NebulaConstant.NebulaJson.COLUMNS.getKey());
            if (Objects.isNull(columns)) {
                return restJson;
            }
            JSONArray data = results.getJSONArray(NebulaConstant.NebulaJson.DATA.getKey());
            if (Objects.isNull(data)) {
                return restJson;
            }
            List<JSONObject> resultList = new ArrayList<>();
            data.stream().map(d -> (JSONObject) d).forEach(d -> {
                JSONArray row = d.getJSONArray(NebulaConstant.NebulaJson.ROW.getKey());
                JSONObject map = new JSONObject();
                for (int i = 0; i < columns.size(); i++) {
                    map.put(columns.getString(i), row.get(i));
                }
                resultList.add(map);
            });
            restJson.put(NebulaConstant.NebulaJson.DATA.getKey(), resultList);
        } catch (Exception e) {
            restJson.put(NebulaConstant.NebulaJson.CODE.getKey(), NebulaConstant.ERROR_CODE);
            restJson.put(NebulaConstant.NebulaJson.MESSAGE.getKey(), e.toString());
            log.error("nebula execute err：", e);
        }
        return restJson;
    }
}
